import { Matrix4 } from "../../utils/Matrix4";
import { Primitives } from "../../utils/Primitives";
import { TextrueUtil } from "../../utils/TextureUtil";
import { Util } from "../../utils/Util";
import { WebGLUtil } from "../../utils/WebGLUtil";
import { IProgramInfo } from "../interface/IDefine";
import { Render } from "../Render";
import { WebglTemplateBase } from "./WebglTemplateBase";

/*
 * @Author: Snow
 * @Date: 2021-10-11 20:35:00
 * @Description: 选中  交互
 */
export class TemplatePicking extends WebglTemplateBase {

    private pickProgramInfo: IProgramInfo;

    protected override initProgram(): void {
        this.vsSource = `
            attribute vec4 a_position;
            attribute vec4 a_color;
            
            uniform mat4 u_matrix;
            
            varying vec4 v_color;
            
            void main() {
                // Multiply the position by the matrix.
                gl_Position = u_matrix * a_position;
                
                // Pass the color to the fragment shader.
                v_color = a_color;
            }
        `;

        this.fsSource = `
            precision mediump float;

            // Passed in from the vertex shader.
            varying vec4 v_color;
            
            uniform vec4 u_colorMult;
            
            void main() {
                gl_FragColor = v_color * u_colorMult;
            }
        `;

        let pickVsSource = `
            attribute vec4 a_position;
    
            uniform mat4 u_matrix;
            
            void main() {
                // Multiply the position by the matrix.
                gl_Position = u_matrix * a_position;
            }
        `;

        let pickFsSource = `
            precision mediump float;

            // 选中ID
            uniform vec4 u_id;
                        
            void main() {
                gl_FragColor = u_id;
            }
        `;

        super.initProgram();

        this.pickProgramInfo = WebGLUtil.createProgramInfo(Render.GL, pickVsSource, pickFsSource);
    }

    protected override initBuffers(): void {
        super.initBuffers();
        const gl = Render.GL;


        const sphereBufferInfo = Primitives.createFlattenedFunc(Primitives.createSphereVertices)(gl, 10, 12, 6);
        const cubeBufferInfo = Primitives.createFlattenedFunc(Primitives.createCubeVertices)(gl, 20);
        const coneBufferInfo = Primitives.createFlattenedFunc(Primitives.createTruncatedConeVertices)(gl, 10, 0, 20, 12, 1, true, false);

        const shapes = [
            sphereBufferInfo,
            cubeBufferInfo,
            coneBufferInfo,
        ];

        //let bufferInfo = WebGLUtil.createBufferInfoFromArrays(gl, arrays);

        let cameraAngleRadians = Util.degToRad(0);
        let fieldOfViewRadians = Util.degToRad(60);
        let cameraHeight = 50;


        let objects = [];
        let objectsToDraw = [];
        let numObjects = 300;
        for (var ii = 0; ii < numObjects; ++ii) {
            let id = ii + 1;
            let object = {
                uniforms: {
                    u_colorMult: [Util.rand(1, 2) / 2, 1, Util.rand(1, 2) / 2, 1],
                    u_matrix: Matrix4.identity(),
                    u_id: [
                        ((id >> 0) & 0xFF) / 0xFF,
                        ((id >> 8) & 0xFF) / 0xFF,
                        ((id >> 16) & 0xFF) / 0xFF,
                        ((id >> 24) & 0xFF) / 0xFF,
                    ],
                },
                translation: [Util.rand(-100, 100), Util.rand(-100, 100), Util.rand(-150, -50)],
                xRotationSpeed: Util.rand(0.8, 1.2),
                yRotationSpeed: Util.rand(0.8, 1.2),
            }
            objects.push(object);

            let drawInfo = {
                programInfo: this.programInfo,
                bufferInfo: shapes[ii % shapes.length],
                uniforms: object.uniforms,
            }
            objectsToDraw.push(drawInfo);
        }
        let targetTexture = gl.createTexture();
        gl.bindTexture(gl.TEXTURE_2D, targetTexture);
        gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MIN_FILTER, gl.LINEAR);
        gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_WRAP_S, gl.CLAMP_TO_EDGE);
        gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_WRAP_T, gl.CLAMP_TO_EDGE);

        let depthBuffer = gl.createRenderbuffer();
        gl.bindRenderbuffer(gl.RENDERBUFFER, depthBuffer);

        let setFramebufferAttachmentSizes = (width: number, height: number) => {
            gl.bindTexture(gl.TEXTURE_2D, targetTexture);
            // define size and format of level 0
            const level = 0;
            const internalFormat = gl.RGBA;
            const border = 0;
            const format = gl.RGBA;
            const type = gl.UNSIGNED_BYTE;
            const data = null;
            gl.texImage2D(gl.TEXTURE_2D, level, internalFormat,
                width, height, border,
                format, type, data);

            gl.bindRenderbuffer(gl.RENDERBUFFER, depthBuffer);
            gl.renderbufferStorage(gl.RENDERBUFFER, gl.DEPTH_COMPONENT16, width, height);
        }

        const frameBuffer = gl.createFramebuffer();
        gl.bindFramebuffer(gl.FRAMEBUFFER, frameBuffer);
        gl.framebufferTexture2D(gl.FRAMEBUFFER, gl.COLOR_ATTACHMENT0, gl.TEXTURE_2D, targetTexture, 0);

        gl.framebufferRenderbuffer(gl.FRAMEBUFFER, gl.DEPTH_ATTACHMENT, gl.RENDERBUFFER, depthBuffer);

        let frameCount = 0;
        let mouseX = -1;
        let mouseY = -1;
        let oldPickNdx = -1;
        let oldPickColor;



        let drawScene = () => {
            ++frameCount;
            let time = this.ts;
            time = time * 0.5 + 5;
            let isResize = WebGLUtil.resizeCanvasToMatchDisplaySize(gl);
            if (isResize) {
                setFramebufferAttachmentSizes(gl.canvas.width, gl.canvas.height);
            }
            // Clear the canvas AND the depth buffer.
            gl.enable(gl.DEPTH_TEST);
            gl.enable(gl.CULL_FACE);
            gl.clear(gl.COLOR_BUFFER_BIT | gl.DEPTH_BUFFER_BIT);

            gl.clearColor(1, 1, 1, 1);


            // Compute the projection matrix
            let aspect = gl.canvas.clientWidth / gl.canvas.clientHeight;
            var projectionMatrix = Matrix4.perspective(fieldOfViewRadians, aspect, 1, 2000);

            // Compute the camera's matrix using look at.
            let cameraPosition = [0, 0, 100];
            let target = [0, 0, 0];
            let up = [0, 1, 0];
            let cameraMatrix = Matrix4.lookAt(cameraPosition, target, up);

            // Make a view matrix from the camera matrix.
            let viewMatrix = Matrix4.invert(cameraMatrix);

            let viewProjectionMatrix = Matrix4.multiply(projectionMatrix, viewMatrix);

            let sphereXRotation = time;
            let sphereYRotation = time;
            let cubeXRotation = -time;
            let cubeYRotation = time;
            let coneXRotation = time;
            let coneYRotation = -time;



            let computeMatrix = (viewProjectionMatrix, translation, xRotation, yRotation): number[] => {
                var matrix = Matrix4.multiply(viewProjectionMatrix, Matrix4.translation(
                    translation[0],
                    translation[1],
                    translation[2]));
                matrix = matrix = Matrix4.multiply(matrix, Matrix4.rotateX(xRotation));
                return matrix = matrix = Matrix4.multiply(matrix, Matrix4.rotateY(yRotation));
            }

            let realDraw = () => {
                for (let index = 0; index < numObjects; index++) {
                    let drawInfo = objectsToDraw[index];
                    let { translation, xRotationSpeed, yRotationSpeed } = objects[index];

                    // Setup all the needed buffers and attributes.
                    WebGLUtil.setBuffersAndAttributes(gl, drawInfo.programInfo, drawInfo.bufferInfo);

                    drawInfo.uniforms.u_matrix = computeMatrix(viewProjectionMatrix, translation, xRotationSpeed * time, yRotationSpeed * time)

                    // Set the uniforms that are the same for all objects.
                    WebGLUtil.setUniforms(drawInfo.programInfo, drawInfo.uniforms);

                    // Draw the geometry.
                    gl.drawArrays(gl.TRIANGLES, 0, drawInfo.bufferInfo.numElements);
                }
            }

            gl.enable(gl.DEPTH_TEST);
            gl.enable(gl.CULL_FACE);
            gl.clear(gl.COLOR_BUFFER_BIT | gl.DEPTH_BUFFER_BIT);

            gl.useProgram(this.pickProgramInfo.program);
            gl.bindFramebuffer(gl.FRAMEBUFFER, frameBuffer);
            objectsToDraw.forEach(drawInfo => {
                drawInfo.programInfo = this.pickProgramInfo;
            });
            realDraw();

            const pixelX = mouseX * gl.canvas.width / gl.canvas.clientWidth;
            ///纹理坐标系是坐下角是0，0点
            const pixelY = gl.canvas.height - mouseY * gl.canvas.height / gl.canvas.clientHeight - 1;

            const data = new Uint8Array(4);

            gl.readPixels(pixelX, pixelY, 1, 1, gl.RGBA, gl.UNSIGNED_BYTE, data);
            const id = data[0] + (data[1] << 8) + (data[2] << 16) + (data[3] << 24);

            // restore the object's color
            if (oldPickNdx >= 0) {
                const object = objects[oldPickNdx];
                object.uniforms.u_colorMult = oldPickColor;
                oldPickNdx = -1;
            }

            // highlight object under mouse
            if (id > 0) {
                const pickNdx = id - 1;
                oldPickNdx = pickNdx;
                const object = objects[pickNdx];
                oldPickColor = object.uniforms.u_colorMult;
                object.uniforms.u_colorMult = (frameCount & 0x8) ? [1, 0, 0, 1] : [1, 1, 0, 1];
            }


            //gl.clear(gl.COLOR_BUFFER_BIT | gl.DEPTH_BUFFER_BIT);
            gl.useProgram(this.programInfo.program);
            objectsToDraw.forEach(drawInfo => {
                drawInfo.programInfo = this.programInfo;
            });
            gl.bindFramebuffer(gl.FRAMEBUFFER, null);
            gl.viewport(0, 0, gl.canvas.width, gl.canvas.height);
            realDraw();
        }
        this.draw = drawScene;

        gl.canvas.addEventListener('mousemove', (e) => {
            const rect = gl.canvas.getBoundingClientRect();
            mouseX = e.clientX - rect.left;
            mouseY = e.clientY - rect.top;
            //console.log(mouseX,mouseY);
        });
    }
}